﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Bittorrent.Bencode;
using Bittorrent.Bencode.Collections;
using Bittorrent.Client.Tracker.Exceptions;
using Bittorrent.Extentions;

namespace Bittorrent.Client.Tracker {
    internal class HttpTrackerResponse {
        internal HttpTrackerResponse(Dictionary dictonary) {
            FailureReason = dictonary.GetOptionalString("failure reason");
            WarningMessage = dictonary.GetOptionalString("warning message");
            Interval = dictonary.GetOptionalInteger("interval");
            MinInterval = dictonary.GetOptionalInteger("min interval");
            TrackerId = dictonary.GetOptionalString("tracker id");
            Complete = dictonary.GetOptionalInteger("complete");
            Incomplete = dictonary.GetOptionalInteger("incomplete");
            Peers = GetPeers(dictonary);
        }

        /// <summary>
        /// Human-readable error message as to why the request failed.
        /// If FailureReason is non empty string, then no other keys may be present.
        /// </summary>
        public string FailureReason { get; private set; }

        /// <summary>
        /// Human-readable warrning message.
        /// </summary>
        public string WarningMessage { get; private set; }

        /// <summary>
        /// Interval in seconds that the client should wait between sending regular
        /// requests to the tracker.
        /// Note: Returns -1 if there is no interval present in the response.
        /// </summary>
        public long Interval { get; private set; }

        /// <summary>
        /// Minimum annouce interval. If present client must not reannouce more
        /// frequently than this.
        /// Note: Returns -1 if there is no min interval present in the repsonse.
        /// </summary>
        public long MinInterval { get; private set; }

        /// <summary>
        /// A string that the client should send back on its next announcements.
        /// If absent (string empty) and a previous announce sent a tracker id,
        /// do not discard the old value.
        /// </summary>
        public string TrackerId { get; private set; }

        /// <summary>
        /// Number of peers with the entire file.
        /// Note: Returns -1 if complete is'nt present in the response.
        /// </summary>
        public long Complete { get; private set; }

        /// <summary>
        /// Number of non-seeder peers.
        /// Note: Returns -1 if incomplete is'nt present in the response.
        /// </summary>
        public long Incomplete { get; private set; }

        /// <summary>
        /// A list of Peers ready for a connection.
        /// </summary>
        public IEnumerable<IPeer> Peers { get; private set; }

        #region private members

        private static IEnumerable<IPeer> GetPeers(Dictionary dictonary) {
            // TODO: This method look like crap have to be fixed.

            if (!dictonary.ContainsKey("peers"))
                yield break;

            var peers = dictonary["peers"];

            if (peers is List) {
                foreach (var peer in ((List)peers)) {
                    if (!(peer is Dictionary))
                        throw new HttpTrackerException(ErrorResource.UnsupportedHttpTrackerPeerListTypeMessage);

                    yield return new DictionaryPeer(((Dictionary)peer));
                }
            }
            else if (peers is ByteString) {
                byte[] bytes = ((ByteString)peers).Bytes;
                if ((bytes.Length % 6) != 0)
                    throw new HttpTrackerException(ErrorResource.CompactPeerListSizeMessage);

                using (var memory = new MemoryStream(bytes)) {
                    var reader = new BinaryReader(memory);

                    while (reader.BaseStream.Position < reader.BaseStream.Length) {
                        var data = new byte[6];
                        reader.Read(data, 0, 6);

                        yield return new BinaryPeer(data);
                    }
                }
            }
            else {
                throw new HttpTrackerException(ErrorResource.UnkownPeerFormatHttpTrackerResponseMessage);
            }
        }

        #endregion
    }
}
